package com.atguigu.gmall.gateway.filter;

import com.atguigu.gmall.common.utils.IpUtils;
import com.atguigu.gmall.common.utils.JwtUtils;
import com.atguigu.gmall.gateway.config.JwtProperties;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

@Component
@EnableConfigurationProperties(JwtProperties.class)
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.PathConfig> {

    @Autowired
    @SuppressWarnings("all")
    private JwtProperties properties;

    public AuthGatewayFilterFactory() {
        super(PathConfig.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("paths");
    }

    @Override
    public ShortcutType shortcutType() {
        return ShortcutType.GATHER_LIST;
    }

    @Override
    public GatewayFilter apply(PathConfig config) {

        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

                // 获取request对象，类似于HttpServletRequest对象
                ServerHttpRequest request = exchange.getRequest();
                ServerHttpResponse response = exchange.getResponse();
                try {

                    // 1.判断当前请求在不在拦截名单中，如果不在则直接放行
                    // 获取当前请求路径
                    String curPath = request.getURI().getPath();
                    // 拦截名单
                    List<String> paths = config.paths;
                    // 如果拦截名单为空，拦截所有路径；如果不为空，判断当前请求在不在拦截名单中
                    if (!CollectionUtils.isEmpty(paths) && !paths.stream().anyMatch(path -> curPath.startsWith(path))){
                        return chain.filter(exchange);
                    }

                    // 2.获取请求中的token：cookie（同步） header(异步)
                    String token = request.getHeaders().getFirst("token");
                    if (StringUtils.isBlank(token)){
                        // 如果头信息没有token，再尝试从cookie中获取token
                        MultiValueMap<String, HttpCookie> cookies = request.getCookies();//获取所有的cookie
                        if (!CollectionUtils.isEmpty(cookies) && cookies.containsKey(properties.getCookieName())){
                            HttpCookie cookie = cookies.getFirst(properties.getCookieName());
                            token = cookie.getValue();
                        }
                    }

                    // 3.判断token是否为空，如果为空则拦截，重定向到登录页面
                    if (StringUtils.isBlank(token)){
                        // 响应状态码：303，代表重定向
                        response.setStatusCode(HttpStatus.SEE_OTHER);
                        // 指定重定向的地址
                        response.getHeaders().add(HttpHeaders.LOCATION, "http://sso.gmall.com/toLogin.html?returnUrl=" + request.getURI());
                        return response.setComplete();
                    }

                    // 4.解析token，如果解析出现异常则拦截，重定向到登录页面
                    Map<String, Object> map = JwtUtils.getInfoFromToken(token, properties.getPublicKey());

                    // 5.判断当前请求的ip地址 和 token中的ip地址是否一致，不一致则拦截，重定向到登录页面
                    String curIp = IpUtils.getIpAddressAtGateway(request); // 当前请求用户的ip地址
                    String ip = map.get("ip").toString();
                    if (!StringUtils.equals(ip, curIp)){
                        // 响应状态码：303，代表重定向
                        response.setStatusCode(HttpStatus.SEE_OTHER);
                        // 指定重定向的地址
                        response.getHeaders().add(HttpHeaders.LOCATION, "http://sso.gmall.com/toLogin.html?returnUrl=" + request.getURI());
                        return response.setComplete();
                    }

                    // 6.把解析后的用户信息传递给后续业务操作
                    //HttpHeaders headers = request.getHeaders();
                    //headers.add("userId", map.get("userId").toString());
                    //headers.add("username", map.get("username").toString());
                    request.mutate()
                            .header("userId", map.get("userId").toString())
                            .header("username", map.get("username").toString())
                            .build();
                    exchange.mutate().request(request).build();

                    // 7.放行
                    return chain.filter(exchange);
                } catch (Exception e) {
                    e.printStackTrace();
                    // 响应状态码：303，代表重定向
                    response.setStatusCode(HttpStatus.SEE_OTHER);
                    // 指定重定向的地址
                    response.getHeaders().add(HttpHeaders.LOCATION, "http://sso.gmall.com/toLogin.html?returnUrl=" + request.getURI());
                    return response.setComplete();
                }
            }
        };
    }

    @Data
    static class PathConfig{
        private List<String> paths;
    }
}
